package server;

import analyticsServer.Event;
import analyticsServer.IAnalyticsServer;
import billingServer.IBillingServerRMI;
import billingServer.IBillingServerSecure;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import security.KeyLoader;
import security.MasterChannel;
import server.data.Bid;

/**
 * Auction-Server, used to listen for Auction-Clients. Users can create
 * Auctions, list Auctions and bid on Auctions. The Server listenes for Messages
 * via TCP and answers to them. The Server is also listening sending
 * UDP-Messages to the client.
 *
 * @author Gruppe 95
 */
public class Server implements Runnable {

    private static final Logger logger = Logger.getLogger("AuctionServer");
    public static String NAME = "Auction";
    private String serverName;
    private int serverPort;
    private IBillingServerRMI billingServer;
    private static IBillingServerSecure billingServerSecure;
    private static IAnalyticsServer analyticsServer;
    private ServerSocket serverSocket;
    private int iPort;
    private List<Socket> clientSockets;
    private ExecutorService executor;
    private boolean bStopped = false;
    private PrivateKey privateKey;
    private boolean interrupt = false;

    /**
     * Constructor creates a new Server and runs it
     *
     * @param port
     */
    public Server(String port, String analyticsBind, String billingBind, String privateKeyFile, String clientKeyDir) throws Exception {
        logger.log(Level.INFO, "private key file {0} ", new Object[]{privateKeyFile});
        //logger.setLevel(Level.OFF);
        this.iPort = Integer.parseInt(port);
        this.clientSockets = new ArrayList<Socket>();

        loadServerProperties();
        privateKey = KeyLoader.loadPrivateKey("auction-server");

        Registry registry = LocateRegistry.getRegistry(serverName, serverPort);
        try {
            billingServer = (IBillingServerRMI) registry.lookup(billingBind);
        } catch (ConnectException cex) {
            System.out.println("Error: cannot connect to BillingServer!");
        }
        try {
            analyticsServer = (IAnalyticsServer) registry.lookup(analyticsBind);
        } catch (ConnectException cex) {
            System.out.println("Error: cannot connect to AnalyticsServer!");
        }
    }

    /**
     * Start server. Build connections to AnalyticsServer and BillingServer.
     */
    public void start() throws NoSuchAlgorithmException, RemoteException {
        logger.info("AuctionServer start.");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update("dslab2012".getBytes());
        String billingServerPassword = new BigInteger(1, md5.digest()).toString(16);
        logger.info("AuctionServer login BillingServer");
        if (billingServer != null) {
            billingServerSecure = billingServer.login("auctionServer", billingServerPassword);
            if (billingServerSecure != null) {
                logger.log(Level.INFO, "AuctionServer connect BillingServer: {0}", billingServerSecure);
            }
        }
    }

    @Override
    public void run() {
        executor = Executors.newCachedThreadPool();
        AuctionManager.getInstance().initTimer();
        try 
        {
            serverSocket = new ServerSocket(iPort);
            while (!bStopped && !interrupt) {
                    Socket clientSocket = serverSocket.accept();
                    clientSockets.add(clientSocket);
                    logger.info("New ClientThread");
                    executor.execute(new TCPServerThread(new MasterChannel(clientSocket), this, privateKey));
            }
        } catch (IOException e) {
            System.out.println("IOException occured while waiting for Clients " + e.getMessage());
        }
   //     stop();
        System.out.println("Server-Main-Thread stops...");
    }

    public synchronized void stop() {
        if (!bStopped) {
            bStopped = true;
            try {
                if (serverSocket != null && !serverSocket.isClosed()) {
                    serverSocket.close();
                }
                logger.log(Level.INFO, "BillingServer: {0}", billingServerSecure);
                billingServerSecure = null;
                logger.log(Level.INFO, "BillingServer: {0}", billingServerSecure);
            } catch (IOException e1) {
                //ignore
            }
            //close all client-connections
            for (Socket s : clientSockets) {
                if (!s.isClosed()) {
                    try {
                        s.close();
                    } catch (IOException e) {
                        //ignore
                    }
                }
            }
            if (executor != null) {
                executor.shutdown();
                executor.shutdownNow();
            }
            AuctionManager.getInstance().stopTimer();
        }
    }
    
    public synchronized void close() {
        if (!interrupt && !bStopped) {
            interrupt = true;        
            for (Socket s : clientSockets) {
                if (!s.isClosed()) {
                    try {
                        s.close();
                    } catch (IOException e) {
                        //ignore
                    }
                }
            }
            try {
                if(serverSocket!=null && !serverSocket.isClosed())
                serverSocket.close();
            } catch (IOException e) {
                //ignore
            }
            if (executor != null) {
                executor.shutdown();
                executor.shutdownNow();
            }
        }
    }    

    public boolean getStopped() {
        return this.bStopped;
    }

    public boolean getInterrupted() {
        return this.interrupt;
    }

    public void setInterrupted(boolean value) {
        this.interrupt=value;
    }

    public void setStopped(boolean value) {
        this.bStopped=value;
    }    
    /**
     * Static method called by AuctionManagen on end of auction. Informs
     * BillingServer on outcome of auction.
     *
     * @param highest
     */
    public static synchronized void callBillingServer(Bid highest) {
        logger.log(Level.INFO, "AuctionServer call BillingServer: Bid {0}", highest);
        try {
            if (billingServerSecure != null) {
                billingServerSecure.billAuction(highest);
            }

        } catch (RemoteException ex) {
            billingServerSecure = null;
            System.out.println("Lost Billing-Server");
        }
    }

    public static synchronized void callAnalyticsServer(Event e) {
        try {
            logger.log(Level.INFO, "AuctionServer call AnalyticsServer: {0}", e);
            if (analyticsServer != null) {
                analyticsServer.processEvent(e);
            }
        } catch (RemoteException ex) {
            analyticsServer = null;
            System.out.println("Lost Analytics-Server");
        }

    }

    /*
     * Load properties for connection to AnalyticsServer and BillingServer.
     */
    private void loadServerProperties() throws Exception {
        InputStream is = ClassLoader.getSystemResourceAsStream("registry.properties");
        if (is != null) {
            Properties tmp = new java.util.Properties();
            try {
                tmp.load(is);
                serverName = tmp.getProperty("registry.host");
                serverPort = Integer.parseInt(tmp.getProperty("registry.port"));
            } catch (IOException iox) {
                //Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, iox);
                throw new Exception("Error while reading Properties file");
            } finally {
                try {
                    is.close();
                } catch (IOException ex) {
                    //ignore
                }
            }
        } else {
            // Logger.getLogger(Client.class.getName()).log(Level.SEVERE, "Properties file not found!");
            throw new Exception("Properties file not found!");
        }
    }
}
